# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import print_function
from collections import OrderedDict
import json
import re
import sys
from typing import Any

description = ""


primitiveTypes = [
    "integer",
    "number",
    "boolean",
    "string",
    "object",
    "any",
    "array",
    "binary",
]


def assignType(
    item: dict, type: str, is_array: bool = False, map_binary_to_string: bool = False
) -> None:
    if is_array:
        item["type"] = "array"
        item["items"] = OrderedDict()
        assignType(item["items"], type, False, map_binary_to_string)
        return

    if type == "enum":
        type = "string"
    if map_binary_to_string and type == "binary":
        type = "string"
    if type in primitiveTypes:
        item["type"] = type
    else:
        item["$ref"] = type


def createItem(
    d: dict, experimental: bool | Any, deprecated: bool | Any, name: str | Any = None
) -> OrderedDict[str, Any]:
    result = OrderedDict(d)
    if name:
        result["name"] = name
    global description
    if description:
        result["description"] = description.strip()
    if experimental:
        result["experimental"] = True
    if deprecated:
        result["deprecated"] = True
    return result


def parse(
    data: str, file_name: str, map_binary_to_string: bool = False
) -> OrderedDict[str, Any]:
    protocol = OrderedDict()
    protocol["version"] = OrderedDict()
    protocol["domains"] = []
    domain = None
    item = None
    subitems = None
    nukeDescription = False
    global description
    lines = data.split("\n")
    for i in range(0, len(lines)):
        if nukeDescription:
            description = ""
            nukeDescription = False
        line = lines[i]
        trimLine = line.strip()

        if trimLine.startswith("#"):
            if len(description):
                description += "\n"
            description += trimLine[2:]
            continue
        else:
            nukeDescription = True

        if len(trimLine) == 0:
            continue

        match = re.compile(r"^(experimental )?(deprecated )?domain (.*)").match(line)
        if match:
            domain = createItem(
                {"domain": match.group(3)}, match.group(1), match.group(2)
            )
            protocol["domains"].append(domain)
            continue

        match = re.compile(r"^  depends on ([^\s]+)").match(line)
        if match:
            if "dependencies" not in domain:
                domain["dependencies"] = []
            domain["dependencies"].append(match.group(1))
            continue

        match = re.compile(
            r"^  (experimental )?(deprecated )?type (.*) "
            r"extends (array of )?([^\s]+)"
        ).match(line)
        if match:
            if "types" not in domain:
                domain["types"] = []
            item = createItem({"id": match.group(3)}, match.group(1), match.group(2))
            assignType(item, match.group(5), match.group(4), map_binary_to_string)
            domain["types"].append(item)
            continue

        match = re.compile(
            r"^  (experimental )?(deprecated )?(command|event) (.*)"
        ).match(line)
        if match:
            list = []
            if match.group(3) == "command":
                if "commands" in domain:
                    list = domain["commands"]
                else:
                    list = domain["commands"] = []
            else:
                if "events" in domain:
                    list = domain["events"]
                else:
                    list = domain["events"] = []

            item = createItem({}, match.group(1), match.group(2), match.group(4))
            list.append(item)
            continue

        match = re.compile(
            r"^      (experimental )?(deprecated )?(optional )?"
            r"(array of )?([^\s]+) ([^\s]+)"
        ).match(line)
        if match:
            param = createItem({}, match.group(1), match.group(2), match.group(6))
            if match.group(3):
                param["optional"] = True
            assignType(param, match.group(5), match.group(4), map_binary_to_string)
            if match.group(5) == "enum":
                enumliterals = param["enum"] = []
            subitems.append(param)
            continue

        match = re.compile(r"^    (parameters|returns|properties)").match(line)
        if match:
            subitems = item[match.group(1)] = []
            continue

        match = re.compile(r"^    enum").match(line)
        if match:
            enumliterals = item["enum"] = []
            continue

        match = re.compile(r"^version").match(line)
        if match:
            continue

        match = re.compile(r"^  major (\d+)").match(line)
        if match:
            protocol["version"]["major"] = match.group(1)
            continue

        match = re.compile(r"^  minor (\d+)").match(line)
        if match:
            protocol["version"]["minor"] = match.group(1)
            continue

        match = re.compile(r"^    redirect ([^\s]+)").match(line)
        if match:
            item["redirect"] = match.group(1)
            continue

        match = re.compile(r"^      (  )?[^\s]+$").match(line)
        if match:
            # enum literal
            enumliterals.append(trimLine)
            continue

        print("Error in %s:%s, illegal token: \t%s" % (file_name, i, line))
        sys.exit(1)
    return protocol


def loads(
    data: str, file_name: str, map_binary_to_string: bool = False
) -> OrderedDict[str, Any] | Any:
    if file_name.endswith(".pdl"):
        return parse(data, file_name, map_binary_to_string)
    return json.loads(data)
